package apii.server;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import apii.algorithm.Algorithm;
import apii.algorithm.ProgressMonitor;

/**
 * <b>Note:</b> Thread SAFE.
 * <p>
 * <b>Note:</b> Creates threads.
 * 
 * UNDER CONSTRUCTION
 * 
 * @author evgeniy
 * 
 */
public class AlgoRunManager {

	private static final int RUNNING = 1;
	private static final int FINISHED_OK = 2;
	private static final int FINISHED_FAIL = 3;

	private final Map<Integer, CodeLabour> activeCodeLabours = new HashMap<Integer, CodeLabour>();
	private Algorithm[] algos;

	public AlgoRunManager(List<String> algoClassNames) throws SQLException {
		try {
			algos = new Algorithm[algoClassNames.size()];
			for (int i = 0; i < algos.length; i++) {
				Iterator<String> iterator = algoClassNames.iterator();
				algos[i] = (Algorithm) Class.forName(iterator.next())
						.getConstructor().newInstance();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		runCodeLabours();
	}

	public boolean runCodeLabour(int codeId) throws SQLException {
		CodeLabour codeLabour = null;
		// check if labour with given codeId is active
		synchronized (activeCodeLabours) {
			if (!activeCodeLabours.containsKey(codeId)) {
				// TODO: object code check if it was checked before
				codeLabour = new CodeLabour(codeId);
				activeCodeLabours.put(codeId, codeLabour);
			}
		}
		if (codeLabour != null) {
			codeLabour.start();
			return true;
		} else {
			return false;
		}
	}

	public double getCodeProgress(int codeId) {
		CodeLabour codeLabour = activeCodeLabours.get(codeId);
		if (codeLabour == null) {
			// TODO: 4to delat?
			return 1;
		} else {
			return codeLabour.getProgress();
		}
	}

	private void runCodeLabours() throws SQLException {
		Connection c = ServerThings.getThings().getConnection();
		Statement s = c.createStatement();
		// TODO: prepared statement
		ResultSet rs = s
				.executeQuery("SELECT CODE_ID FROM APII.CODES WHERE APII.CODES.CHECKED = FALSE");
		while (rs.next()) {
			int codeId = (Integer) rs.getObject(1);
			runCodeLabour(codeId);
		}
	}

	private class ProgressMonitorImpl implements ProgressMonitor {
		private int codeId;
		private String algoName;
		private volatile double progress;

		private static final double MINIMAL_STEP = 0.01;

		public ProgressMonitorImpl(int codeId) {
			this.codeId = codeId;
		}

		@Override
		public void setProgress(double progress) {
			if (progress > this.progress + MINIMAL_STEP) {
				this.progress = progress;
			}
		}

		public double getProgress() {
			return progress;
		}
	}

	/**
	 * TODO: remake
	 * 
	 * @author evgeniy
	 * 
	 */
	private class CodePretendentIterator implements Iterator<Integer> {

		private int i = 0;
		private int codeId;

		private CodePretendentIterator(int codeId) {
			this.codeId = codeId;
		}

		@Override
		public boolean hasNext() {
			return i < codeId;
		}

		@Override
		public Integer next() {
			if (!hasNext()) {
				throw new IllegalStateException();
			}
			i++;
			return i;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	/**
	 * <b>Note:</b> Thread UNSAFE.
	 * <p>
	 * <b>Note:</b> Doesn't create threadz
	 * 
	 * @author evgeniy
	 */
	private class CodeLabour {
		public ProgressMonitorImpl[] pMonitors;
		private AlgoThread[] algoThreads;
		private int codeId;
		Connection c;

		public CodeLabour(int codeId) throws SQLException {
			this.codeId = codeId;
			c = ServerThings.getThings().getConnection();
			pMonitors = new ProgressMonitorImpl[algos.length];
			algoThreads = new AlgoThread[algos.length];
			for (int i = 0; i < algos.length; i++) {
				pMonitors[i] = new ProgressMonitorImpl(codeId);
				algoThreads[i] = new AlgoThread(codeId, algos[i],
						new CodePretendentIterator(codeId), pMonitors[i], c,
						this);
			}
		}

		public void start() {
			for (int i = 0; i < algoThreads.length; i++) {
				algoThreads[i].start();
			}
		}

		public double getProgress() {
			double result = 0;
			for (int i = 0; i < pMonitors.length; i++) {
				result += pMonitors[i].getProgress();
			}
			result /= pMonitors.length;
			return result;
		}

		/**
		 * Method is called from all the algorithm threads
		 */
		private void onThreadFinished(AlgoThread invoker, boolean success) {

			int allThreadsState = FINISHED_OK;
			synchronized (this) {
				if (success) {
					invoker.threadState = FINISHED_OK;
				} else {
					invoker.threadState = FINISHED_FAIL;
				}

				// only downgreade is possible: FINISHED_OK -> RUNNING ->
				// FINISHED_FAIL
				// or FINISHED_OK -> FINISHED_FAIL
				for (int i = 0; i < algoThreads.length; i++) {
					if (algoThreads[i].threadState == RUNNING) {
						if (allThreadsState == FINISHED_OK) {
							allThreadsState = RUNNING;
						}
					} else if (algoThreads[i].threadState == FINISHED_FAIL) {
						allThreadsState = FINISHED_FAIL;
						break;
					} else if (algoThreads[i].threadState != FINISHED_OK) {
						throw new Error();
					}
				}
			}

			System.out.println("ASSERT1");
			try {
				if (allThreadsState == FINISHED_OK) {

					Statement s = c.createStatement();
					s.executeUpdate("UPDATE CODES SET APII.CODES.CHECKED = TRUE "
							+ "WHERE APII.CODES.CODE_ID = " + codeId);
					System.out
							.println("executing update statement with codeid: "
									+ codeId);
//					 c.commit();
				} else if (allThreadsState == FINISHED_FAIL) {
					System.out.println("FINISHED_FAIL: " + codeId);
				} else if (allThreadsState != RUNNING) {
					throw new Error();
				}
			} catch (SQLException ex) {
				// TODO
				ex.printStackTrace();
			} finally {
				synchronized (activeCodeLabours) {
					activeCodeLabours.remove(codeId);
				}
			}
		}
	}

	private class AlgoThread extends Thread {
		private int codeId;
		private Algorithm algo;
		private Iterator<Integer> iterator;
		private ProgressMonitor pm;
		private Connection c;
		private CodeLabour codeLabour;
		public int threadState;

		public AlgoThread(int codeId, Algorithm algo,
				Iterator<Integer> iterator, ProgressMonitor pm, Connection c,
				CodeLabour codeLabour) {
			this.algo = algo;
			this.codeId = codeId;
			this.iterator = iterator;
			this.pm = pm;
			this.c = c;
			this.codeLabour = codeLabour;
		}

		public void run() {
			try {
				System.out.println("thread for id: " + codeId + "; algo: "
						+ algo.getClass().getName() + " start");
				algo.inspect(c, codeId, iterator, pm);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
				}
				System.out.println("thread for id: " + codeId + "; algo: "
						+ algo.getClass().getName() + " end");
				codeLabour.onThreadFinished(this, true);
			} catch (SQLException e) {
				codeLabour.onThreadFinished(this, false);
				e.printStackTrace();
			} finally {
			}
		}
	}
}